Una de las tareas más utilizadas en la programación es la búsqueda de subcadenas o patrones dentro de otras cadenas de texto.
Las expresiones regulares, también conocidas como 'regex' o 'regexp', son patrones de búsqueda definidos con una sintaxis formal. Siempre que sigamos sus reglas, podremos realizar búsquedas simples y avanzadas, que utilizadas en conjunto con otras funcionalidades, las vuelven una de las opciones más útiles e importantes de cualquier lenguaje.
Sin embargo antes de utilizarlas hay que estar seguros de lo que hacemos, de ahí aquella famosa frase de Jamie Zawinski, programador y hacker:
Some people, when confronted with a problem, think "I know, I'll use regular expressions." Now they have two problems.
Que viene a decir:
Hay gente que, cuando se enfrenta a un problema, piensa "Ya sé, usaré expresiones regulares". Ahora tienen dos problemas.
In [42]:
import re
texto = "En esta cadena se encuentra una palabra mágica"
re.search('mágica', texto)
Out[42]:
Como vemos, al realizar la búsqueda lo que nos encontramos es un objeto de tipo Match (encontrado), en lugar un simple True o False.
En cambio, si no se encontrase la palabra, no se devolvería nada (None):
In [43]:
re.search('hola', texto)
Por tanto, podemos utilizar la propia funcionalidad junto a un condicional sin ningún problema:
In [45]:
palabra = "mágica"
encontrado = re.search(palabra, texto)
if encontrado:
print("Se ha encontrado la palabra:", palabra)
else:
print("No se ha encontrado la palabra:", palabra)
Sin embargo, volviendo al objeto devuelto de tipo Match, éste nos ofrece algunas opciones interesantes.
In [57]:
print( encontrado.start() ) # Posición donde empieza la coincidencia
print( encontrado.end() ) # Posición donde termina la coincidencia
print( encontrado.span() ) # Tupla con posiciones donde empieza y termina la coincidencia
print( encontrado.string ) # Cadena sobre la que se ha realizado la búsqueda
Como vemos, en este objeto se esconde mucha más información de la que parece a simple vista, luego seguiremos hablando de ellos.
In [273]:
texto = "Hola mundo"
re.match('Hola', texto)
In [275]:
texto = "Hola mundo"
re.match('Mola', texto) # no devuelve nada
In [52]:
texto = "Vamos a dividir esta cadena"
re.split(' ', texto)
Out[52]:
In [228]:
texto = "Hola amigo"
re.sub('amigo', 'amiga', texto)
Out[228]:
In [287]:
texto = "hola adios hola hola"
re.findall('hola', texto)
Out[287]:
Aquí se nos devuelve una lista, pero podríamos aplicar la función len() para saber el número:
In [288]:
len(re.findall('hola', texto))
Out[288]:
In [290]:
texto = "hola adios hello bye"
re.findall('hola|hello', texto)
Out[290]:
In [119]:
texto = "hla hola hoola hooola hooooola"
Antes de continuar, y para aligerar todo el proceso, vamos a crear una función capaz de ejecutar varios patrones en una lista sobre un texto:
In [121]:
def buscar(patrones, texto):
for patron in patrones:
print( re.findall(patron, texto) )
patrones = ['hla', 'hola', 'hoola']
buscar(patrones, texto)
In [125]:
patrones = ['ho','ho*','ho*la','hu*la'] # 'ho', 'ho[0..N]', 'ho[0..N]la', 'hu[0..N]la'
buscar(patrones, texto)
In [127]:
patrones = ['ho*', 'ho+'] # 'ho[0..N], 'ho[1..N]'
buscar(patrones, texto)
In [128]:
patrones = ['ho*', 'ho+', 'ho?', 'ho?la'] # 'ho[0..N], 'ho[1..N]', 'ho[0..1]', 'ho[0..1]la'
buscar(patrones, texto)
In [129]:
patrones = ['ho{0}la', 'ho{1}la', 'ho{2}la'] # 'ho[0]la', 'ho[1]la', 'ho[2]la'
buscar(patrones, texto)
In [130]:
patrones = ['ho{0,1}la', 'ho{1,2}la', 'ho{2,9}la'] # 'ho[0..1]la', 'ho[1..2]la', 'ho[2..9]la'
buscar(patrones, texto)
In [134]:
texto = "hala hela hila hola hula"
patrones = ['h[ou]la', 'h[aio]la', 'h[aeiou]la']
buscar(patrones, texto)
Evidentemente los podemos utilizar con repeticiones:
In [142]:
texto = "haala heeela hiiiila hoooooola"
patrones = ['h[ae]la', 'h[ae]*la', 'h[io]{3,9}la']
buscar(patrones, texto)
In [216]:
texto = "hala hela hila hola hula"
patrones = ['h[o]la', 'h[^o]la']
buscar(patrones, texto)
Si excluimos una expresión regular con un grupo excluido, en realidad tenemos lo mismo:
Otra característica que hace ultra potentes los grupos, es la capacidad de definir rangos. Ejemplos de rangos:
Tened en cuenta que cualquier rango puede ser excluido para conseguir el patrón contrario.
In [223]:
texto = "hola h0la Hola mola m0la M0la"
patrones = ['h[a-z]la', 'h[0-9]la', '[A-z]{4}', '[A-Z][A-z0-9]{3}']
buscar(patrones, texto)
Código | Significado |
---|---|
\d | numérico |
\D | no numérico |
\s | espacio en blanco |
\S | no espacio en blanco |
\w | alfanumérico |
\W | no alfanumérico |
El problema que encontraremos en Python a la hora de definir código escapado, es que las cadenas no tienen en cuenta el \ a no ser que especifiquemos que son cadenas en crudo (raw), por lo que tendremos que precedir las expresiones regulares con una 'r'.
In [270]:
texto = "Este curso de Python se publicó en el año 2016"
patrones = [r'\d+', r'\D+', r'\s', r'\S+', r'\w+', r'\W+']
buscar(patrones, texto)
Por mi parte lo vamos a dejar aquí, pero el mundo de las expresiones regulares es gigantesco y daría para un curso entero. Os animo a seguir aprendiendo leyendo documentación y buscando ejemplos.
Aquí os dejo algunos enlaces que quizá os pueden servir, en inglés:
Hay docenas y docenas de códigos especiales, si queréis echar un vistazo a todos ellos podéis consultar la documentación oficial:
Un resumen por parte de Google Eduactión:
Otro resumen muy interesante sobre el tema:
Un par de documentos muy trabajados con ejemplos básicos y avanzados: